﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using Newtonsoft.Json;
using CommandLine;

// Sending e-mail via Office365
using Microsoft.Exchange.WebServices.Autodiscover;
using Microsoft.Exchange.WebServices.Data;

namespace AIC_Notification
{
    class Utility
    {
        private StreamWriter LogStream = null;
        private StreamWriter ErrorStream = null;
        private ExchangeService service = null;
        private string Account = "";
        private string Password = "";
        private DayCount CountWorkingDay = null;

        public Utility()
        {
        }

        public void Initialize()
        {
            // Open streams
            LogStream = new StreamWriter(Global.LogPathFile);
            ErrorStream = new StreamWriter(Global.ErrorPathFile);

            // Create DayCount Object
            string HOLIDAY_RESTDAY_FILENAME = "HolidayRestday.json";
            using (StreamReader sr = new StreamReader(Global.InputDirectory + HOLIDAY_RESTDAY_FILENAME))
            {
                string temp = sr.ReadToEnd();
                CountWorkingDay = JsonConvert.DeserializeObject<DayCount>(temp);
            }
        }

        public TimeSpan WorkingDaysBetween(DateTime firstDay, DateTime lastDay)
        {
            return CountWorkingDay.WorkingDaysBetween(firstDay, lastDay);
        }

        public void InitializeEmail(string account, string password)
        {
            int RetryCount = 0;

            // Setup Office 365
            Account = account;
            Password = password;

            if (Global.SendReportAsEmail && service == null)
            {
                // Create the binding.
                service = new ExchangeService();

                // Set the credentials for the on-premises server.
                service.Credentials = new WebCredentials(Account, Password);

                while(RetryCount <= 5)
                {
                    try
                    {
                        // Set the URL.
                        service.AutodiscoverUrl(Account, RedirectionCallback);
                        return;
                    }
                    catch (Exception ex)
                    {
                        // handle exception
                        RetryCount += 1;
                        Global.SysUtil.Log(String.Format("Account: {0} invalid (Retry {1})", Account, RetryCount), 2);

                        if (RetryCount > 5)
                            throw ex;
                    }
                }
            }
        }

        public void Log(string Message, int Level=0)
        {
            LogStream.WriteLine("{0:T}, {1}, {2}", Global.Now(), Level, Message);
            if (Level == 2)
                ErrorStream.WriteLine("{0:T}, {1}, {2}", Global.Now(), Level, Message);

            if (Level >= Global.LogDisplayLevel)
            {
                if (Statistics.ProcessingBoard > 0 && Statistics.TotalBoard > 0)
                {
                    double percent = (double)Statistics.ProcessingBoard / (double)Statistics.TotalBoard * 100;
                    Console.WriteLine(String.Format("{0}/{1} {2:0.00}% - {3}", Statistics.ProcessingBoard, Statistics.TotalBoard, percent, Message));
                }
                else
                {
                    Console.WriteLine(Message);
                }
            }
        }

        public void Log(string Message, int Level, string BoardName, string ListName)
        {
            LogStream.WriteLine("{0:T}, {1}, {2} (B:{3}, L:{4})", Global.Now(), Level, Message, BoardName, ListName);
            if (Level == 2)
                ErrorStream.WriteLine("{0:T}, {1}, {2} (B:{3}, L:{4})", Global.Now(), Level, Message, BoardName, ListName);

            if (Level >= Global.LogDisplayLevel)
            {
                if (Statistics.ProcessingBoard > 0 && Statistics.TotalBoard > 0)
                {
                    double percent = (double)Statistics.ProcessingBoard / (double)Statistics.TotalBoard * 100;
                    Console.WriteLine(String.Format("{0}/{1} {2:0.00}% - {3}", Statistics.ProcessingBoard, Statistics.TotalBoard, percent, Message));
                }
                else
                {
                    Console.WriteLine(Message);
                }
            }
        }

        public void Flush()
        {
            ErrorStream.Flush();
            LogStream.Flush();
        }

        public void FlushClose()
        {
            ErrorStream.Flush();
            ErrorStream.Close();
            ErrorStream = null;

            LogStream.Flush();
            LogStream.Close();
            LogStream = null;
        }

        ~Utility()
        {
            if (ErrorStream != null)
            {
                ErrorStream.Flush();
                ErrorStream.Close();
            }
            if (LogStream != null)
            {
                LogStream.Flush();
                LogStream.Close();
            }
        }

        public void ParseCommandLine(string[] args)
        {
            //var options = new CommandLineOptions();
            // Parse in 'strict mode', success or quit

            var result = Parser.Default.ParseArguments<CommandLineOptions>(args);

            string[] Reports = null;

            result.WithParsed(options => Statistics.ApplicationType = options.ApplicationType);
            result.WithParsed(options => Statistics.RunType = options.RunType);
            result.WithParsed(options => Global.ProcessTaskList = options.Task ? true : false);
            result.WithParsed(options => Global.SendReportAsEmail = options.Email ? true : false);
            // result.WithParsed(options => Reports = options.Reports);

            // result.WithNotParsed(errors => ...) // errors is a sequence of type IEnumerable<Error>
            if (Reports != null)
            {
                foreach (var Report in Reports)
                    AddReport(Global.Reports, Report);
            }
        }

        public void AddReport(IList<FormatedReport> Reports, string ReportName)
        {
            switch (ReportName)
            {
                case "epac":
                    Reports.Add(new EssayPendingReportForAC(false));
                    break;
                case "epacd":
                    Reports.Add(new EssayPendingReportForAC(true));
                    break;
                case "epam":
                    Reports.Add(new EssayPendingReportForAM(false));
                    break;
                case "epamd":
                    Reports.Add(new EssayPendingReportForAM(true));
                    break;
                case "ssac":
                    Reports.Add(new StudentStatusReportForAC(StudentStatusReportForAC.DisplayTable.partial));
                    break;
                case "ssam":
                    Reports.Add(new StudentStatusReportForAM(StudentStatusReportForAC.DisplayTable.partial));
                    break;
                case "sslt":
                    Reports.Add(new StudentStatusReportForLT());
                    break;
                case "sss":
                    Reports.Add(new StudentStatusReportForStudent());
                    break;
                case "tslt":
                    Reports.Add(new TeacherStatusForLT());
                    break;
                case "gssac":
                    Reports.Add(new GuidanceStudentStatusForAC());
                    break;
                case "gssam":
                    Reports.Add(new GuidanceStudentStatusForAM());
                    break;
                case "bi":
                    Reports.Add(new BoardIssueReport());
                    break;
                case "slac":
                    Reports.Add(new SchoolListForAC());
                    break;
                case "slam":
                    Reports.Add(new SchoolListForAM());
                    break;
                default:
                    Console.WriteLine("Unrecognized Report: {0}", ReportName);
                    break;
            }
        }

        public SchoolAdmissionStatistics.SchoolListType LookupAdmissionResultSchoolType(SOF_Application ApplyingStudent, School ApplyingSchool)
        {
            StudentAdmissionResult SchoolResult = null;

            if (ApplyingStudent.StudentObject != null && ApplyingSchool != null)
                SchoolResult = ApplyingStudent.StudentObject.FindAdmissionResult(ApplyingSchool);

            if (SchoolResult != null)
            {
                if (SchoolResult.Category.Equals("Reach School", StringComparison.CurrentCultureIgnoreCase))
                    return SchoolAdmissionStatistics.SchoolListType.Reach;
                else if (SchoolResult.Category.Equals("Solid School", StringComparison.CurrentCultureIgnoreCase))
                    return SchoolAdmissionStatistics.SchoolListType.Match;
                else if (SchoolResult.Category.Equals("Safety School", StringComparison.CurrentCultureIgnoreCase))
                    return SchoolAdmissionStatistics.SchoolListType.Safety;
                else
                    return SchoolAdmissionStatistics.SchoolListType.Unknown;
            }
            else
                return SchoolAdmissionStatistics.SchoolListType.Unknown;
        }

        public SchoolAdmissionStatistics.SchoolListType CalculateSchoolType(SOF_Application ApplyingStudent, School ApplyingSchool)
        {
            SchoolAdmissionStatistics.SchoolListType SchoolType = SchoolAdmissionStatistics.SchoolListType.Unknown;
            if ((ApplyingStudent.StudentObject != null) && (ApplyingSchool.AdmissionResultObj != null))
            {
                int MIN_RELEVANT_RESULT = 4;

                int StudentSAT = (ApplyingStudent.StudentObject.ScoreObject != null) ? ApplyingStudent.StudentObject.ScoreObject.SAT_Total() : 0;
                int StudentTOEFL = (ApplyingStudent.StudentObject.ScoreObject != null) ? (int)ApplyingStudent.StudentObject.ScoreObject.TOEFL_Total : 0;

                // If there are enough relevant data
                if (ApplyingSchool.AdmissionResultObj.Normal_Count >= MIN_RELEVANT_RESULT)
                {
                    SchoolType = ApplyingSchool.AdmissionResultObj.GetSchoolType(StudentSAT, StudentTOEFL);

                    if (SchoolType == SchoolAdmissionStatistics.SchoolListType.Safety)
                    {
                        if (ApplyingSchool.RankingInt > 0 && ApplyingSchool.RankingInt <= 20)
                        {
                            SchoolType = SchoolAdmissionStatistics.SchoolListType.Reach;
                        }
                        else if (ApplyingSchool.RankingInt > 20 && ApplyingSchool.RankingInt <= 30)
                        {
                            SchoolType = SchoolAdmissionStatistics.SchoolListType.Match;
                        }
                        else
                        {
                            if (ApplyingSchool.AdmissionResultObj.Safety_Count == 0)
                                SchoolType = SchoolAdmissionStatistics.SchoolListType.Match;
                        }
                    }
                    else if (SchoolType == SchoolAdmissionStatistics.SchoolListType.Match)
                    {
                        if (ApplyingSchool.RankingInt > 0 && ApplyingSchool.RankingInt <= 20)
                            SchoolType = SchoolAdmissionStatistics.SchoolListType.Reach;
                    }
                }
                // Not enough data for normal decision but can judge whether a safety school
                else
                {
                    if (ApplyingSchool.AdmissionResultObj.Safety_Count >= MIN_RELEVANT_RESULT)
                    {
                        SchoolType = ApplyingSchool.AdmissionResultObj.GetSafetySchoolType(StudentSAT, StudentTOEFL);
                    }
                }
            }

            if (SchoolType == SchoolAdmissionStatistics.SchoolListType.Unknown)
            {
                if (ApplyingSchool.RankingInt > 0 && ApplyingSchool.RankingInt <= 20)
                    SchoolType = SchoolAdmissionStatistics.SchoolListType.Reach;
                else if (ApplyingSchool.RankingInt > 100)
                    SchoolType = SchoolAdmissionStatistics.SchoolListType.Safety;
            }

            return SchoolType;
        }

        public void ConfirmBeforeBegin(int WaitSeconds)
        {
            // Display Run Configuration
            if (Statistics.ApplicationType == ApplicationType.sof)
                Global.SysUtil.Log("Application-Type: SOF");
            else if (Statistics.ApplicationType == ApplicationType.guidance)
                Global.SysUtil.Log("Application-Type: Guidance");
            if (Statistics.RunType == RunType.full)
                Global.SysUtil.Log("Run-Type: Full");
            else if (Statistics.RunType == RunType.update)
                Global.SysUtil.Log("Run-Type: Update");
            else if (Statistics.RunType == RunType.cache)
                Global.SysUtil.Log("Run-Type: Cache");
            else if (Statistics.RunType == RunType.skip)
                Global.SysUtil.Log("Run-Type: Skip");
            else
                Global.SysUtil.Log("Run-Type: Unknown");
            Global.SysUtil.Log(Global.ProcessTaskList ? "Task: Enable" : "Task: Disable");
            Global.SysUtil.Log(Global.SendReportAsEmail ? "Email: Enable" : "Email: Disable");
            foreach (var Report in Global.Reports)
                Global.SysUtil.Log(String.Format("Running Report: {0}", Report.ReportTitle));

            // Wait x seconds before proceeding (in case of cancel)
            Console.Write("Starting in: ");
            for (int i = WaitSeconds; i > 0; i--)
            {
                Console.Write(i + " ");
                System.Threading.Thread.Sleep(1000);
            }
            Console.WriteLine();
        }

        static bool RedirectionCallback(string url)
        {
            // Return true if the URL is an HTTPS URL.
            Global.SysUtil.Log("Redirected to: " + url);
            return url.ToLower().StartsWith("https://");
        }

        public void SendHTML(IList<string> toRecipients, IList<string> ccRecipients, string subject, string htmlBody, IList<string> attachments = null)
        {
            int RetryCount = 0;

            while (RetryCount <= 5)
            {
                try
                {
                    if (service != null)
                    {
                        EmailMessage emailMessage = new EmailMessage(service);

                        if (toRecipients != null)
                        {
                            List<string> D_toRecipients = toRecipients.Distinct().ToList();
                            foreach (string toRecipient in D_toRecipients)
                                emailMessage.ToRecipients.Add(toRecipient);
                        }

                        if (ccRecipients != null)
                        {
                            List<string> D_ccRecipients = ccRecipients.Distinct().ToList();
                            foreach (string ccRecipient in D_ccRecipients)
                                emailMessage.CcRecipients.Add(ccRecipient);
                        }

                        emailMessage.Subject = subject;

                        emailMessage.Body = new MessageBody(BodyType.HTML, htmlBody);

                        if (attachments != null)
                        {
                            foreach (var attachment in attachments)
                                emailMessage.Attachments.AddFileAttachment(attachment);
                        }

                        Console.WriteLine(String.Format("Sending E-mail: {0}", subject));

                        if (Global.SendReportAsEmail) // Skip sending e-mail if turned off
                            emailMessage.Send();
                    }

                    return;
                }
                catch (Exception ex)
                {
                    RetryCount += 1;
                    Console.WriteLine(String.Format("Send e-mail timed-out; retry {0};", RetryCount));

                    //if (RetryCount > 5)
                    //    throw ex;
                }
            }
        }

        public bool ReadBoardsFromJson(string filename)
        {
            bool ReadSuccess = false;

            using (StreamReader sr = new StreamReader(filename))
            {
                string temp = sr.ReadToEnd();
                if (Statistics.ApplicationType == ApplicationType.sof)
                {
                    Global.SOF_Applications = JsonConvert.DeserializeObject<IDictionary<string, SOF_Application>>(temp);
                    if (Global.SOF_Applications != null)
                        ReadSuccess = true;
                    else
                        Global.SOF_Applications = new Dictionary<string, SOF_Application>();
                }
                else if (Statistics.ApplicationType == ApplicationType.guidance)
                {
                    Global.Guidance_Applications = JsonConvert.DeserializeObject<IDictionary<string, Guidance_Application>>(temp);
                    if (Global.Guidance_Applications != null)
                        ReadSuccess = true;
                    else

                        Global.Guidance_Applications = new Dictionary<string, Guidance_Application>();
                }
            }

            return ReadSuccess;
        }

        public void WriteBoardsToJson(string filename)
        {
            // Write Students Object to Json
            using (StreamWriter sw = new StreamWriter(filename))
            {
                string temp = "";
                if (Statistics.ApplicationType == ApplicationType.sof)
                    temp = JsonConvert.SerializeObject(Global.SOF_Applications, Formatting.Indented);
                else if (Statistics.ApplicationType == ApplicationType.guidance)
                    temp = JsonConvert.SerializeObject(Global.Guidance_Applications, Formatting.Indented);
                sw.WriteLine(temp);
            }
        }
    }
}
